# -*- coding: UTF-8 -*-
"Les filtres par défaut"

import template, re, random

###################
# CHAÎNES         #
###################

def addslashes(value, _):
    "Ajoute des antislashs à la valeur (utile pour passer une chaîne à JavaScript, par exemple)."
    return value.replace('"', '\\"').replace("'", "\\'")

def capfirst(value, _):
    "Met en majuscule le premier caractère de la valeur."
    value = str(value)
    return value and value[0].upper() + value[1:]

def fix_ampersands(value, _):
    "Remplace les & par des entités ``&amp;``."
    from django.utils.html import fix_ampersands
    return fix_ampersands(value)

def floatformat(text, _):
    """
    Affiche un nombre à virgule flottante avec un seul chiffre après la virgule, et ce seulement
    lorsque l'affichage d'une virgule est nécessaire.
    """
    if not text:
        return ''
    if text - int(text) < 0.1:
        return int(text)
    return "%.1f" % text

def linenumbers(value, _):
    "Affiche le texte avec des numéros de ligne."
    from django.utils.html import escape
    lines = value.split('\n')
    # On cherche le nombre de chiffres maximum que pourra prendre un numéro de ligne afin de
    # l'employer avec le code de formatage
    width = str(len(str(len(lines))))
    for i, line in enumerate(lines):
        lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    return '\n'.join(lines)

def lower(value, _):
    "Convertit une chaîne en minuscules."
    return value.lower()

def make_list(value, _):
    """
    Renvoie la valeur sous forme de liste. Si la valeur est un entier, elle devient une liste
    de chiffres. Si la valeur est une chaîne, elle devient une liste de caractères.
    """
    return list(str(value))

def slugify(value, _):
    "Convertit une chaîne en minuscules, supprime les caractères non-alphabétiques et convertit les espaces en tirets."
    value = re.sub('[^\w\s]', '', value).strip().lower()
    return re.sub('\s+', '-', value)

def stringformat(value, arg):
    """
    Formate la variable en fonction de l'argument, qui doit être un code de formatage.
    Celui-ci doit suivre la syntaxe Python, la seule exception étant que le "%" du début
    est enlevé.

    Voyez http://docs.python.org/lib/typesseq-strings.html pour plus d'informations sur
    le formatage des chaînes en Python.
    """
    try:
        return ("%" + arg) % value
    except (ValueError, TypeError):
        return ""

def title(value, _):
    "Met en majuscule le premier caractère de chaque mot de la chaîne; si un mot est précédé d'une apostrophe, sa première lettre est mise en minuscule."
    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())

def truncatewords(value, arg):
    """
    Tronque une chaîne après un certain nombre de mots.

    Argument : le nombre de mots avant de commencer à tronquer
    """
    from django.utils.text import truncate_words
    try:
        length = int(arg)
    except ValueError: # valeur invalide passée à int()
        return value # le filtre échoue de manière silencieuse
    if not isinstance(value, basestring):
        value = str(value)
    return truncate_words(value, length)

def upper(value, _):
    "Convertit une chaîne en majuscules."
    return value.upper()

def urlencode(value, _):
    "Échappe la valeur de telle façon à ce qu'elle puisse être incluse au sein d'une URL."
    import urllib
    return urllib.quote(value)

def urlize(value, _):
    "Convertit les URL littérales en liens cliquables."
    from django.utils.html import urlize
    return urlize(value, nofollow=True)

def urlizetrunc(value, limit):
    """
    Convertit les URL littérales en liens cliquables, en tronquant ces URL après la limite
    spécifiée.

    Argument : la longueur de l'URL avant de commencer à tronquer
    """
    from django.utils.html import urlize
    return urlize(value, trim_url_limit=int(limit), nofollow=True)

def wordcount(value, _):
    "Renvoie le nombre de mots de la valeur."
    return len(value.split())

def wordwrap(value, arg):
    """
    Ajoute automatiquement des sauts de ligne après la longueur spécifiée.

    Argument : le nombre de mots avant d'ajouter un saut de ligne
    """
    from django.utils.text import wrap
    return wrap(value, int(arg))

def ljust(value, arg):
    """
    Inclut la valeur au sein d'une chaîne de longueur $arg; la valeur
    sera à gauche de la chaîne et les caractères restants seront des espaces.

    Argument : taille de la chaîne
    """
    return str(value).ljust(int(arg))

def rjust(value, arg):
    """
    Inclut la valeur au sein d'une chaîne de longueur $arg; la valeur
    sera à droite de la chaîne et les caractères restants seront des espaces.

    Argument : taille de la chaîne
    """
    return str(value).rjust(int(arg))

def center(value, arg):
    "Inclut la valeur au sein d'une chaîne dont la longueur est l'argument spécifié; la valeur sera au centre de la chaîne."
    return str(value).center(int(arg))

def cut(value, arg):
    "Supprime de la chaîne le caractère ou la sous-chaîne spécifiée."
    return value.replace(arg, '')

###################
# CHAÎNES HTML    #
###################

def escape(value, _):
    "Échappe le code HTML contenu dans la chaîne."
    from django.utils.html import escape
    return escape(value)

def linebreaks(value, _):
    "Convertit les sauts de ligne en balises <p> et <br />."
    from django.utils.html import linebreaks
    return linebreaks(value)

def linebreaksbr(value, _):
    "Convertit les sauts de ligne en balises <br />."
    return value.replace('\n', '<br />')

def removetags(value, tags):
    "Supprime de la chaîne une série de balises [X]HTML spécifiée au sein d'une liste (les balises de cette liste doivent être séparées entre elles par des espaces)."
    tags = [re.escape(tag) for tag in tags.split()]
    tags_re = '(%s)' % '|'.join(tags)
    starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re)
    endtag_re = re.compile('</%s>' % tags_re)
    value = starttag_re.sub('', value)
    value = endtag_re.sub('', value)
    return value

def striptags(value, _):
    "Supprime de la chaîne toutes les balises [X]HTML."
    from django.utils.html import strip_tags
    if not isinstance(value, basestring):
        value = str(value)
    return strip_tags(value)

###################
# LISTES          #
###################

def dictsort(value, arg):
    """
    Accepte une liste de dictionnaires comme valeur et renvoie cette même liste triée par la
    propriété spécifiée en argument.
    """
    decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    decorated.sort()
    return [item[1] for item in decorated]

def dictsortreversed(value, arg):
    """
    Accepte une liste de dictionnaires comme valeur et renvoie cette même liste triée par la
    propriété spécifiée en argument, le tri se faisant à l'envers de l'ordre naturel.
    """
    decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    decorated.sort()
    decorated.reverse()
    return [item[1] for item in decorated]

def first(value, _):
    "Renvoie le premier élément d'une liste."
    try:
        return value[0]
    except IndexError:
        return ''

def join(value, arg):
    "Fusionne une liste en une chaîne, tout comme ``chaine.join(liste)`` en Python."
    try:
        return arg.join(map(str, value))
    except AttributeError: # le filtre échoue de manière silencieuse mais correctement
        return value

def length(value, _):
    "Renvoie la longueur de la valeur (utile pour les listes)."
    return len(value)

def length_is(value, arg):
    "Renvoie une valeur booléenne en fonction du résultat de la comparaison exacte entre la longueur de la valeur et l'argument."
    return len(value) == int(arg)

def random(value, _):
    "Renvoie un élément au hasard de la liste."
    return random.choice(value)

def slice_(value, arg):
    """
    Renvoie une tranche de la liste.

    Emploie la même syntaxe que les tranches Python; voyez
    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
    pour une introduction rapide.
    """
    try:
        start, finish = arg.split(':')
    except ValueError: # l'argument spécifié est incorrect
        return value # le filtre échoue de manière silencieuse mais correctement
    try:
        if start and finish:
            return value[int(start):int(finish)]
        if start:
            return value[int(start):]
        if finish:
            return value[:int(finish)]
    except TypeError:
        pass
    return value

def unordered_list(value, _):
    """
    Accepte une liste de listes imbriquées et renvoie du code HTML décrivant la
    liste non-ordonnée correspondante (SANS les balises <ul> et </ul> qui encadrent
    la liste).

    La liste est supposée être dans un format correct. À titre d'exemple, si ``var`` contient
    ``['États', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
    alors ``{{ var|unordered_list }}`` renverra le code HTML suivant :

    ::

        <li>États
        <ul>
                <li>Kansas
                <ul>
                        <li>Lawrence</li>
                        <li>Topeka</li>
                </ul>
                </li>
                <li>Illinois</li>
        </ul>
        </li>
    """
    def _helper(value, tabs):
        indent = '\t' * tabs
        if value[1]:
            return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
                '\n'.join([unordered_list(v, tabs+1) for v in value[1]]), indent, indent)
        else:
            return '%s<li>%s</li>' % (indent, value[0])
    return _helper(value, 1)

###################
# ENTIERS         #
###################

def add(value, arg):
    "Ajoute l'argument à la valeur."
    return int(value) + int(arg)

def get_digit(value, arg):
    """
    Renvoie le chiffre demandé du nombre entier, 1 correspondant au chiffre
    le plus à droite, 2 l'avant-dernier chiffre le plus à droite... Renvoie
    la valeur d'origine en cas d'entrée invalide (si l'entrée ou l'argument
    n'est pas un entier ou si l'argument est inférieur à 1). Sinon, la sortie
    sera toujours un entier.
    """
    try:
        arg = int(arg)
        value = int(value)
    except ValueError:
        return value # Le filtre échoue de manière silencieuse en cas d'argument invalide
    if arg < 1:
        return value
    try:
        return int(str(value)[-arg])
    except IndexError:
        return 0

###################
# DATES           #
###################

def date(value, arg):
    "Met en forme une date en fonction du format spécifié."
    from django.utils.dateformat import format
    return format(value, arg)

def time(value, arg):
    "Met en forme une heure en fonction du format spécifié."
    from django.utils.dateformat import time_format
    return time_format(value, arg)

def timesince(value, _):
    "Met en forme une date pour qu'elle apparaisse sous forme du temps depuis cette date (par exemple \"4 jours et 6 heures\")."
    from django.utils.timesince import timesince
    return timesince(value)

###################
# LOGIQUE         #
###################

def default(value, arg):
    "Si la valeur est indisponible, on renvoie la valeur par défaut spécifiée en argument."
    return value or arg

def divisibleby(value, arg):
    "Renvoie True si la valeur est divisible par l'argument."
    return int(value) % int(arg) == 0

def yesno(value, arg):
    """
    Accepte en argument une chaîne associant aux valeurs booléennes True, False ou None
    (facultatif) des chaînes arbitraires et renvoie l'une des chaînes en
    fonction de la valeur :

    ==========  =========================  =====================================
    Valeur      Argument                   Résultat
    ==========  =========================  =====================================
    ``True``    ``"oui,non,peut-être"``    ``oui``
    ``False``   ``"oui,non,peut-être"``    ``non``
    ``None``    ``"oui,non,peut-être"``    ``peut-être``
    ``None``    ``"oui,non"``              ``"non"`` (convertit d'abord None
                                           en False si aucune association n'est
                                           spécifiée pour None)
    ==========  =========================  =====================================
   """
    bits = arg.split(',')
    if len(bits) < 2:
        return value # Argument invalide.
    try:
        yes, no, maybe = bits
    except ValueError: # pas de valeur pour None; on adapte en conséquence les valeurs
        yes, no, maybe = bits, bits[1]
    if value is None:
        return maybe
    if value:
        return yes
    return no

###################
# DIVERS          #
###################

def filesizeformat(bytes, _):
    """
    Met en forme la valeur pour qu'elle constitue une taille de fichier 'lisible' (par exemple 13 Ko, 4.1 Mo,
    102 octets etc).
    """
    bytes = float(bytes)
    if bytes < 1024:
        return "%d octet%s" % (bytes, bytes != 1 and 's' or '')
    if bytes < 1024 * 1024:
        return "%.1f Ko" % (bytes / 1024)
    if bytes < 1024 * 1024 * 1024:
        return "%.1f Mo" % (bytes / (1024 * 1024))
    return "%.1f Go" % (bytes / (1024 * 1024 * 1024))

def pluralize(value, _):
    "Renvoie 's' si la valeur est différente de 1."
    try:
        if int(value) != 1:
            return 's'
    except ValueError: # chaîne invalide qui n'est pas un nombre
        pass
    except TypeError: # la valeur n'est ni une chaîne ni un nombre; une liste?
        try:
            if len(value) != 1:
                return 's'
        except TypeError: # on ne peut obtenir la longueur d'un objet n'en ayant pas
            pass
    return ''

def phone2numeric(value, _):
    "Accepte un numéro de téléphone comme valeur et le convertit en son équivalent numérique."
    from django.utils.text import phone2numeric
    return phone2numeric(value)

def pprint(value, _):
    "Un alias de pprint.pprint (seulement à des fins de débogage)."
    from pprint import pformat
    return pformat(value)

# Syntaxe : template.register_filter(nom du filtre, fonction, un_argument?)
template.register_filter('add', add, True)
template.register_filter('addslashes', addslashes, False)
template.register_filter('capfirst', capfirst, False)
template.register_filter('center', center, True)
template.register_filter('cut', cut, True)
template.register_filter('date', date, True)
template.register_filter('default', default, True)
template.register_filter('dictsort', dictsort, True)
template.register_filter('dictsortreversed', dictsortreversed, True)
template.register_filter('divisibleby', divisibleby, True)
template.register_filter('escape', escape, False)
template.register_filter('filesizeformat', filesizeformat, False)
template.register_filter('first', first, False)
template.register_filter('fix_ampersands', fix_ampersands, False)
template.register_filter('floatformat', floatformat, False)
template.register_filter('get_digit', get_digit, True)
template.register_filter('join', join, True)
template.register_filter('length', length, False)
template.register_filter('length_is', length_is, True)
template.register_filter('linebreaks', linebreaks, False)
template.register_filter('linebreaksbr', linebreaksbr, False)
template.register_filter('linenumbers', linenumbers, False)
template.register_filter('ljust', ljust, True)
template.register_filter('lower', lower, False)
template.register_filter('make_list', make_list, False)
template.register_filter('phone2numeric', phone2numeric, False)
template.register_filter('pluralize', pluralize, False)
template.register_filter('pprint', pprint, False)
template.register_filter('removetags', removetags, True)
template.register_filter('random', random, False)
template.register_filter('rjust', rjust, True)
template.register_filter('slice', slice_, True)
template.register_filter('slugify', slugify, False)
template.register_filter('stringformat', stringformat, True)
template.register_filter('striptags', striptags, False)
template.register_filter('time', time, True)
template.register_filter('timesince', timesince, False)
template.register_filter('title', title, False)
template.register_filter('truncatewords', truncatewords, True)
template.register_filter('unordered_list', unordered_list, False)
template.register_filter('upper', upper, False)
template.register_filter('urlencode', urlencode, False)
template.register_filter('urlize', urlize, False)
template.register_filter('urlizetrunc', urlizetrunc, True)
template.register_filter('wordcount', wordcount, False)
template.register_filter('wordwrap', wordwrap, True)
template.register_filter('yesno', yesno, True)
